﻿#ifndef YUTOPP_BASIC_API_HPP
#define YUTOPP_BASIC_API_HPP

#include "../utility/utility.hpp"
#include "../url_base64.hpp"

#include <string>
#include <map>

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

#include <boost/assign.hpp>

#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/has_xxx.hpp>

#include <boost/filesystem/path.hpp>			//ファイルパスの解析
#include <boost/algorithm/string/case_conv.hpp>	//tolower

#include <boost/preprocessor/debug/assert.hpp>
#include <boost/preprocessor/control.hpp>
#include <boost/preprocessor/seq.hpp>
#include <boost/preprocessor/tuple.hpp>
#include <boost/preprocessor/comparison.hpp>

#include <boost/preprocessor/facilities/apply.hpp>
#include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/facilities/is_empty.hpp>
#include <boost/preprocessor/facilities.hpp>
#include <boost/preprocessor/tuple/elem.hpp>

#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>

#define API_ENTRY_START( name, path_string, http_method, data_t ) \
	API_ENRTY_START_ROOT( ::webapp, name, path_string, http_method, data_t )

#define API_ENRTY_START_ROOT( ns, name, path_string, http_method, data_t ) \
	struct name \
	{ \
		typedef ns ## ::clients::request::tag_ ## http_method ## _method	method; \
		typedef ns ## ::clients::request::content:: ## data_t				data_type; \
		 \
		static const char* /*const*/ path() \
		{ \
			return path_string; \
		}/* \
		 \
		/*static const wchar_t* const wpath() \
		{ \
			return L path; \
		}*/

#define API_GET_PARAMETER_TYPE( data_t, type ) \
	API_GET_PARAMETER_TYPE_IMPL( ::webapp, data_t, type )

#define API_GET_PARAMETER_TYPE_IMPL( ns, data_t, type ) \
	ns ## ::clients::request::content:: ## data_t ## ::traits< type >::parameter_type

#define API_GROUP( name ) \
	typedef name group_type;

#define API_TEMPLATE_TYPE_IMPL( type, has_prefix, prefix ) \
	(( type, has_prefix, prefix ))( 0 )

#define API_TEMPLATE_TYPE( type ) \
	API_TEMPLATE_TYPE_IMPL( type, 0, 0 )

#define API_TEMPLATE_TYPE_STRING() \
	API_TEMPLATE_TYPE_IMPL( std::string, 0, 0 )

#define API_TEMPLATE_TYPE_WSTRING() \
	API_TEMPLATE_TYPE_IMPL( std::wstring, 1, L )

#define API_PARAMETER( type, parameter, default_value, name ) \
	(( type, parameter, default_value, name ))

#define API_PARAMETER_STRING( parameter, default_value, name ) \
	API_PARAMETER( const std::string&, parameter, default_value, name )

#define API_PARAMETER_WSTRING( parameter, default_value, name ) \
	API_PARAMETER( const std::wstring&, parameter, default_value, name )

#define API_PARAMETER_LIST_RETURN( tuple ) \
	data_type::traits< ## BOOST_PP_TUPLE_ELEM( 3, 0, tuple ) ## >::parameter_type

#define API_RETURN_TYPE( type ) \
	data_type::traits< type >::parameter_type

#define API_PARAMETER_TYPE( type ) \
	typename type::data_type::traits< type >::parameter_type


#define API_PARAMETER_LIST_ARG_DEF( tuple ) \
	BOOST_PP_TUPLE_ELEM( 4, 0, tuple ) \
	BOOST_PP_TUPLE_ELEM( 4, 1, tuple ) \
	BOOST_PP_IF( \
		BOOST_PP_IS_EMPTY( \
			BOOST_PP_CAT( \
				BOOST_PP_EMPTY, \
				BOOST_PP_TUPLE_ELEM( 4, 2, tuple ) \
				)() \
			) , \
		, \
		= BOOST_PP_TUPLE_ELEM( 4, 2, tuple ) \
		)

#define API_PARAMETER_LIST_ARG_DEF_CAT( unused, n, seq ) \
		BOOST_PP_COMMA_IF( n ) \
		API_PARAMETER_LIST_ARG_DEF( BOOST_PP_SEQ_ELEM( n, seq ) )

#define API_PARAMETER_LIST_ARG_DEF_MANY( seq ) \
	BOOST_PP_LPAREN() \
	BOOST_PP_TUPLE_ELEM( 3, 0, BOOST_PP_SEQ_ELEM( 0, seq ) )*, \
	BOOST_PP_REPEAT( \
		BOOST_PP_SUB( BOOST_PP_SEQ_SIZE( seq ), 2 ), \
		API_PARAMETER_LIST_ARG_DEF_CAT, \
		BOOST_PP_SEQ_REST_N( 2, seq ) \
		) \
	) const

#define API_PARAMETER_LIST_ARG_DEF_NONE \
	( void* ) const

#define API_PARAMETER_LIST_ARG( seq ) \
	BOOST_PP_IF( \
		BOOST_PP_GREATER( BOOST_PP_SEQ_SIZE( seq ), 2 ), \
		API_PARAMETER_LIST_ARG_DEF_MANY( seq ), \
		API_PARAMETER_LIST_ARG_DEF_NONE )

#define API_PARAMETER_LIST_IMPL_DEF( unused1, prefix, tuple ) \
	( \
	BOOST_PP_CAT( \
		BOOST_PP_IF( \
			BOOST_PP_EQUAL( BOOST_PP_TUPLE_ELEM( 3, 1, prefix ), 1 ), \
			BOOST_PP_TUPLE_ELEM( 3, 2, prefix ), \
			), \
		BOOST_PP_IF( \
			BOOST_PP_IS_EMPTY( \
				BOOST_PP_CAT( \
					BOOST_PP_EMPTY, \
					BOOST_PP_TUPLE_ELEM( 4, 3, tuple ) \
					)() \
				), \
			BOOST_PP_STRINGIZE( BOOST_PP_TUPLE_ELEM( 4, 1, tuple ) ), \
			BOOST_PP_TUPLE_ELEM( 4, 3, tuple ) \
			) \
		)\
	, BOOST_PP_TUPLE_ELEM( 4, 1, tuple ) \
	)

#define API_PARAMETER_LIST_IMPL( seq ) \
	 \
	BOOST_PP_IF( \
		BOOST_PP_GREATER( BOOST_PP_SEQ_SIZE( seq ), 2 ), \
		BOOST_PP_CAT( \
			boost::assign::map_list_of, \
			BOOST_PP_SEQ_FOR_EACH( \
				API_PARAMETER_LIST_IMPL_DEF, \
				BOOST_PP_SEQ_ELEM( 0, seq ), \
				BOOST_PP_SEQ_REST_N( 2, seq ) \
				) \
			), \
		API_PARAMETER_LIST_RETURN( BOOST_PP_SEQ_ELEM( 0, seq ) )() \
		)

#define API_PARAMETER_LIST_OWNER( seq ) \
	API_PARAMETER_LIST_RETURN( BOOST_PP_SEQ_ELEM( 0, seq ) ) operator() \
	API_PARAMETER_LIST_ARG( seq )

#define API_PARAMETER_LIST( seq ) \
	API_PARAMETER_LIST_OWNER( seq ) { return API_PARAMETER_LIST_IMPL(seq); }

#define API_MAKE_RETUTN_VAL( type, name ) \
	API_RETURN_TYPE( type ) name

#define API_ENTRY_END \
	};

namespace webapp
{
	namespace clients
	{
		namespace request
		{
			namespace detail
			{
				struct body_skipper {
					template<typename T>
					T& operator()( T& t ) const { return t; }
				};

				struct address_skipper {
					template<typename T, typename U>
					T* operator()( T* t, const U& ) const { return t; }
				};		
			}

			namespace content
			{
				namespace _url_encoded
				{
					namespace detail
					{
						template<typename Encoder, typename T>
						std::pair<typename Encoder::result_type, typename Encoder::result_type>
							encode( const T& t )
						{
							Encoder e;
	
							return std::make_pair( url::encode( e( t.first ) ), url::encode( e( t.second ) ) );
						}
					}

					// application/x-www-form-urlencoded
					struct url_encoded {
						template<typename String>
						struct traits;

						const char* const operator()() const
						{
							return "application/x-www-form-urlencoded";
						}
					};


					template<typename String>
					struct url_encoded::traits
					{
						typedef std::string	binary_type;
						typedef String		string_type;
						typedef
							std::map<string_type, string_type>
						parameter_type;

						template<typename Encoder>
						struct generator
						{
							binary_type operator()( const parameter_type& parameter ) const
							{
								typedef std::pair<binary_type, binary_type> ret_pair;
								binary_type ret;									
	
								BOOST_FOREACH(
									const ret_pair& v,
									parameter
										| boost::adaptors::transformed( detail::encode<Encoder, parameter_type::value_type> )
									) {
										if ( !v.second.empty() )
											ret += v.first + "=" + v.second + "&";
								}

								if ( !ret.empty() )
									ret.erase( ret.end() - 1 );

								return ret;
							}
						};
					};
				} // - namespace _url_encoded

				namespace _form_data
				{
					namespace detail
					{
						template<typename String>
						struct basic_holder
						{
							String value;
							bool is_file;
						};

						template<typename Encoder, typename T>
						std::pair<typename Encoder::result_type, basic_holder<typename Encoder::result_type> >
							encode( const T& t )
						{
							Encoder e;
							const basic_holder<typename Encoder::result_type>
								h = { e( t.second.value ), t.second.is_file };	//!!!
	
							return std::make_pair( e( t.first ), h );
						}
					}

					// multipart/form-data
					struct form_data
					{
						template<typename String>
						struct traits;
	
						form_data()
							: boundary_( util::make_boundary_string() )
						{}
	
						explicit form_data( const std::string& boundary )
							: boundary_( boundary )
						{}
	
						const std::string operator()() const
						{
							return "multipart/form-data; boundary=" + boundary_;
						}
	
						const std::string& get_boundary() const
						{
							return boundary_;
						}
	
					private:
						std::string boundary_;
					};


					template<typename String>
					struct form_data::traits
					{
						typedef std::string	binary_type;
						typedef String		string_type;
						typedef
							detail::basic_holder<string_type>
						holder;
						typedef
							std::map<string_type, holder>
						parameter_type;

						template<typename Encoder>
						struct generator
						{
							binary_type operator()( const binary_type& boundary, const parameter_type& parameter ) const
							{
								typedef
									std::pair<binary_type, detail::basic_holder<binary_type> >
									ret_pair;
								std::basic_stringstream<binary_type::value_type> ss;
	
								BOOST_FOREACH(
									const ret_pair& v,
									parameter
										| boost::adaptors::transformed( detail::encode<Encoder, parameter_type::value_type> )
									) {
									ss << "--" << boundary << "\r\n";
	
									if ( !v.second.is_file ) {
										ss << "Content-Disposition: form-data; name=\"" << v.first << "\"\r\n";
										ss << "\r\n";
										ss << v.second.value << "\r\n";
									} else {
										const boost::filesystem::path path = v.second.value;
										const binary_type ext = boost::algorithm::to_lower_copy( path.extension().string() );
										//content-type : todo *replace*
										const std::string content_type( ( ( ext == ".png" ) && ( ext == ".gif" ) && ( ext == ".jpg" ) && ( ext == ".jpeg" ) ) ? "image" : "video" );
							
										//バイナリ読み込み
										const std::vector<char> bin( util::load_binary_file( path.string() ) );
	
										ss << "Content-Disposition: form-data; name=\"" << v.first << "\"; filename=\"" << path.filename().string() << "\"\r\n";
										ss << "Content-Type: " << content_type << "/" << std::string( ++ext.begin(), ext.end() ) << "\r\n";
										ss << "Content-Transfer-Encoding: binary" << "\r\n";
										ss << "Content-Length: " << bin.size() << "\r\n";
										ss << "\r\n";
										ss.write( &bin[0], bin.size() );
										ss << "\r\n";
									}
								}
	
								ss << "\r\n";
								ss << "--" << boundary << "--" << std::flush;
								return ss.str();
							}
						};
					};

				} // - namespace _form_data

				namespace _atom
				{
					namespace detail
					{
						template<typename Encoder, typename TreeFrom, typename TreeTo>
						void encode( const TreeFrom& src, TreeTo& dst )
						{
							Encoder e;

							BOOST_FOREACH( const TreeFrom::value_type& val, src ) {
								const TreeFrom& tree = val.second;

								if ( tree.empty() ) {
									if ( !tree.data().empty() ) {
										dst.add( e( val.first ), e( tree.data() ) );
									}
								} else {
									TreeTo& d = dst.add( e( val.first ), e( tree.data() ) );
									encode<Encoder>( tree, d );
								}
							}
						}
					}

					// application/atom+xml
					struct atom
					{
						template<typename String>
						struct traits;

						const char* const operator()() const
						{
							return "application/atom+xml";
						}
					};

					template<typename String>
					struct atom::traits
					{
						typedef std::string	binary_type;
						typedef String		string_type;

						typedef
							boost::property_tree::basic_ptree<
								string_type, string_type, std::less<string_type>
							>
						parameter_type;

						template<typename Encoder>
						struct generator
						{
							binary_type operator()( const parameter_type& tree ) const
							{
								std::basic_stringstream<binary_type::value_type> ss;
								boost::property_tree::basic_ptree<
									binary_type, binary_type, std::less<binary_type>
								> to;

								detail::encode<Encoder>( tree, to );

								boost::property_tree::write_xml(
									ss,
									to,
									boost::property_tree::xml_writer_make_settings(
										' ',
										2,
										boost::property_tree::xml_parser::widen<char>( "utf-8" )
										)
									);

								return ss.str();
							}
						};
					};
				}  // - namespace _atom


				using _url_encoded::url_encoded;
				using _form_data::form_data;
				using _atom::atom;
			}


			BOOST_MPL_HAS_XXX_TRAIT_DEF( meta_body_process )
			template<typename T, typename = void>
			struct meta_body_process
			{
				typedef detail::body_skipper type;
			};
			template<typename T>
			struct meta_body_process<T, typename boost::enable_if<has_meta_body_process<T> >::type>
			{
				typedef typename T::meta_body_process type;
			};

			BOOST_MPL_HAS_XXX_TRAIT_DEF( meta_address_process )
			template<typename T, typename = void>
			struct meta_address_process
			{
				typedef detail::address_skipper type;
			};
			template<typename T>
			struct meta_address_process<T, typename boost::enable_if<has_meta_address_process<T> >::type>
			{
				typedef typename T::meta_address_process type;
			};


			//
			struct tag_get_method {};
			struct tag_post_method {};
			struct tag_put_method {};
			struct tag_delete_method {};

			template<typename T, class = void>
			struct request_method;

			template<typename T>
			struct request_method<T, typename boost::enable_if<boost::is_same<typename T::method, tag_get_method> >::type>
			{
				static const char* name()
				{
					return "GET";
				}
			};

			template<typename T>
			struct request_method<T, typename boost::enable_if<boost::is_same<typename T::method, tag_post_method> >::type>
			{
				static const char* name()
				{
					return "POST";
				}
			};

			template<typename T>
			struct request_method<T, typename boost::enable_if<boost::is_same<typename T::method, tag_put_method> >::type>
			{
				static const char* name()
				{
					return "PUT";
				}
			};

			template<typename T>
			struct request_method<T, typename boost::enable_if<boost::is_same<typename T::method, tag_delete_method> >::type>
			{
				static const char* name()
				{
					return "DELETE";
				}
			};
		} // - api
	} // - clients
} // - webapp

#endif /*YUTOPP_BASIC_API_HPP*/
